<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>虚拟列表-动态高度</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      ul,
      ol {
        list-style: none;
      }

      html,
      body {
        height: 100%;
      }

      body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 20px;
      }

      .container {
        height: 500px;
        border: 1px solid;
        overflow-y: scroll;
      }

      .list {
        width: 500px;
        box-sizing: border-box;
      }

      .item {
        display: flex;
        justify-content: center;
        align-items: center;
        border-bottom: 1px solid gray;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <button class="btn">渲染数据</button>
    <div class="container">
      <ul class="list">
        <li class="item">1</li>
      </ul>
    </div>
    <script>
      // 一开始是不知道动态是多少？那就预估一个 item 高度
      // 1. 渲染之后我们是知道高度的，这儿就需要记录下来，去矫正 list 高度，矫正占位 paddingTop
      const $container = document.querySelector('.container')
      const $list = document.querySelector('.list')
      const $btn = document.querySelector('.btn')
      const datas = new Array(100000).fill(0).map((v, i) => `<li class="item" data-i="${i}" style="height: ${Math.floor(Math.random() * 100) + 28}px">${i}#${Math.random().toString(36).slice(2)}</li>`)
      let startIndex = 0;
      let pageSize = 20; // 一页多少条
      const guessHeight = 57; // 每一项的猜测的高度
      let totalHeight = guessHeight * datas.length; // 猜测总的高度
      const cache = {}

      function render(paddingTop){
        const endIndex = startIndex + pageSize
        $list.style.paddingTop = `${paddingTop}px`;
        $list.innerHTML = datas.slice(startIndex, endIndex).join("")
        requestAnimationFrame(() => {
          Array.from($list.children).forEach((item) => {
            const index = item.dataset.i;
            if(typeof cache[index] === 'undefined') {
              // item.offsetHeight 实际的高度
              cache[index] = item.offsetHeight
              // 还原总高度的真实性
              totalHeight = totalHeight - guessHeight + item.offsetHeight
            }
          })
          // 更新还原之后的总高度
          $list.style.height = `${totalHeight}px`;
        })
      }

      // 点击按钮，类似与模拟请求
      $btn.addEventListener('click', function(e) {
        // 产生猜测的滚动条
        $list.style.height = `${totalHeight}px`;
        render(0)
      })

      $container.addEventListener('scroll', function(e) {
        const scrollTop = this.scrollTop // 滚多少距离

        let offsetHeight = 0;
        let paddingTop = 0;
        for(let i = 0; i < datas.length; i++){
          // 某一项的高度
          const itemHeight = cache[i] ?? guessHeight
          offsetHeight += itemHeight
          paddingTop += itemHeight
          if(offsetHeight > scrollTop) {
            startIndex = i;
            // startIndex 之前的项都是移除掉的
            paddingTop -= itemHeight;
            break;
          }
        }

        render(paddingTop)
      })
    </script>
  </body>
</html>
